#include "xlocal_fs.h"

#include "xrt_error.h"
#include "xfile.h"
#include "xarchive.h"
#include "xdir.h"
#include "xfs.h"
#include "xfile_general_operation.h"
#include "xstring.h"

#include <windows.h>

namespace {

  using xge::File;
  using xge::FilePtr;
  using xge::Archive;
  using xge::ArchivePtr;
  using xge::FileSystem;
  using xge::Dir;
  using xge::DirPtr;
  using xge::String;
  using xge::FileSearch;

  // WFile is a win32 local file  
  // It heavily used the Win32 API instead of direct use of the CRT or STD stuff
  // the fundamental reason is using CRT or STD will cause the target API form a
  // internal memory buffer for the file query , in fact , it is useless in many
  // situation , we use system API directly can only have a internal kernel buffer
  // instead of too many user buffer .
  class WFile : public File {
  public:
    // Load the content into the buffer
    virtual size_t   Load  ( void* buffer , size_t size   );
    virtual int64_t Length() ;
    virtual int64_t Seek  ( int64_t offset , int position );
    // calling this function internally to create the file 
    // since we do not use try catch , so we should avoid 
    // the penalty of the CTor , namely no return value to indicate
    // the status .
    bool CreateFileInternal ( const char_t* file_path ) ;
    virtual ~WFile();
  private:
    // file handler of a win32 file 
    ::HANDLE file_handler_;
  };

  // WDirectory archive is a win32 local directory ( not a file )
  // This is typically a directory not a file . So you can not read
  // from it .It only provide way to do the query of this archive.
  // In addition , the XDirectory may change , so we do not implement
  // any cache stuff to omptimizing the query operation .
  class WDirectory : public Archive {
  public:
    virtual ~WDirectory() {}
    // Load an archive by relative path
    // eg:
    // This archive has been specified by the path :
    // C:\MyFolder\Picture\Something .
    // Than the LoadArchive should pass a parameter
    // Level\Entity\XMLFile
    // than the path can be specified as C:\MyFolder\Picture\Something\Level\Entity\XMLFile

    virtual ArchivePtr LoadArchive( const char_t* path ) {
      // load the archive from here 
      // it is very simple to do this , we just trust that there
      // do exist such file , and load it using API
      // 1. make the path here
      size_t roll_back = current_path_.size();
      current_path_+=_xT('\\');
      current_path_+=path;
      // 2. load a directory
      WDirectory* dic = new WDirectory();
      if( dic->CreateDirectoryInternal( current_path_.c_str()) ) {
        current_path_.resize(roll_back);
        return ArchivePtr(dic);
      } else {
        delete dic;
        current_path_.resize(roll_back);
        return ArchivePtr();
      }
    }

    virtual FilePtr    LoadFile   ( const char_t* path ) {
      size_t roll_back = current_path_.size();
      current_path_+=_xT('\\');
      current_path_+=path;
      WFile * file = new WFile();
      if( file->CreateFileInternal(current_path_.c_str()) ) {
        current_path_.resize(roll_back);
        return FilePtr(file);
      } else {
        delete file;
        current_path_.resize(roll_back);
        return FilePtr();
      }
    }
    // create the directory ptr
    // it basically check if there do exist a certain Directory in
    // the current path . If so , create this virtual object 
    // it basically have the path to this folder .
    bool CreateDirectoryInternal  ( const char_t* current_path );
  private:
    // Implement a internal WDirectoryDir 
    class WDirectoryDir : public Dir {
    public:
      virtual ~WDirectoryDir() {}
      virtual void Next() {
        search_.Next();
      }
      virtual bool HasNext() const {
        return search_.HasNext();
      }
      virtual int Type () {
        if( search_.is_directory() ) {
          return Dir::TYPE_ARCHIVE;
        } else {
          return Dir::TYPE_FILE;
        }
      }
      virtual FilePtr File()  {
        // it is not a file
        if( !search_.is_file() ) 
          return FilePtr();
        // we create this file handler
        WFile* file = new WFile();
        search_path_+=name();
        if( !file->CreateFileInternal(search_path_.c_str()) ) {
          search_path_.resize(rollback_size);
          delete file;
          return FilePtr();
        }
        search_path_.resize(rollback_size);
        return FilePtr(file);
      }

      virtual ArchivePtr Archive() {
        // check if this is a directory
        if( search_.is_directory() ) {
          // make the search path have the archive name
          // this name must be a real name , just name 
          search_path_+=name();
          WDirectory* dic = new WDirectory();
          // can not find such directory 
          if( !dic->CreateDirectoryInternal(search_path_.c_str()) ) {
            delete dic;
            search_path_.resize(rollback_size);
            return ArchivePtr();
          }
          search_path_.resize(rollback_size);
          return ArchivePtr(dic);
        }
        return ArchivePtr();
      }

      virtual const String& name() {
        return name_.assign(search_.file_name());
      }

      WDirectoryDir( const String& search_path ) {
        // copy the upper path
        // since the upper path is something like :
        // C:\MyFolder\Something
        // we just make it as 
        // C:\MyFolder\Something\
        //
        search_path_ = search_path;
        search_path_+=_xT('\\');
        // check out the current string size to ensure
        // we can roll back
        rollback_size = search_path_.size();
        search_path_.reserve( rollback_size + MAX_PATH );
        // add a star to make the windows 32 wildcard
        search_path_+=_xT('*');
        search_.Research(search_path_.c_str());
        // roll back
        search_path_.resize(rollback_size);
        name_.reserve(MAX_PATH);
      }

    private:
      // handle search
      FileSearch search_;
      // handle search path
      // this path has a redundant \
      //

      String search_path_;
      // using a XString to meet the return type requirement of XString type
      String name_;
      size_t rollback_size ;
    };

    virtual DirPtr Begin() {
      return DirPtr( new WDirectoryDir( current_path_ ) );
    }
  private:
    // current path of this directory
    String current_path_;
  };

  // A windows 32 local file system implementation 
  // Windows32 Local File System will not provide the XDir implementation , since
  // it is useless to iterating all the driver in the system .

  class WFileSystem : public FileSystem {
  public:
    virtual const String& plug_name() const {
      return name();
    }
  public:
    // Existed stuff
#ifdef XGE_USE_WIDE_CHAR
#define GET_FILE_ATTRIBUTE ::GetFileAttributesW
#else
#define GET_FILE_ATTRIBUTE ::GetFileAttributesA
#endif
    static bool IsExisted( const char_t* path ) {
      return GET_FILE_ATTRIBUTE(path) != INVALID_FILE_ATTRIBUTES ;
    }
    static bool IsFileExisted( const char_t* path ) {
      DWORD dwRet = GET_FILE_ATTRIBUTE(path) ;
      return ( dwRet != INVALID_FILE_ATTRIBUTES ) &&
             ( (dwRet & FILE_ATTRIBUTE_DIRECTORY) ==0 );
    }
    static bool IsArchiveExisted( const char_t* path ) {
      DWORD dwRet = GET_FILE_ATTRIBUTE(path);
      return ( dwRet != INVALID_FILE_ATTRIBUTES ) &&
             ( (dwRet & FILE_ATTRIBUTE_DIRECTORY) !=0 );
    }
#undef GET_FILE_ATTRIBUTE
  public:
    virtual bool IsExisted(const String& path) {
      return IsExisted(path.c_str());
    }
    virtual bool IsFileExisted(const String& path) {
      return IsFileExisted(path.c_str());
    }
    virtual bool IsArchiveExisted(const String& path) {
      return IsArchiveExisted(path.c_str());
    }

  public:
    // Win32 file system dtor
    WFileSystem() : FileSystem( xge::GetLocalFileSystemName()) {}
    virtual ~WFileSystem() {}
  public:
    // Load function 
    virtual FilePtr LoadFile( const char_t* path_name ) {
      WFile * file = new WFile();
      if( file->CreateFileInternal(path_name) == false ) {
        delete file;
        return FilePtr();
      } 
      return FilePtr(file);
    }
    // Load function
    virtual ArchivePtr LoadArchive( const char_t* path_name ) {
      WDirectory* dic = new WDirectory();
      if( dic->CreateDirectoryInternal(path_name) == false) {
        delete dic;
        return ArchivePtr();
      }
      return ArchivePtr(dic);
    }
  };


  // WFile

  bool  WFile::CreateFileInternal( const char_t* file_path ) {
    // create the file internally 
    file_handler_ = 
#ifdef XGE_USE_WIDE_CHAR
    ::CreateFileW(
      file_path,
      GENERIC_READ,
      FILE_SHARE_READ,
      NULL,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      NULL
      );
#else
      ::CreateFileA(
      file_path,
      GENERIC_READ,
      FILE_SHARE_READ,
      NULL,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      NULL
      );
#endif

    if( file_handler_ == INVALID_HANDLE_VALUE ) {
      WIN32_API_WARNING(_xT("CreateFile"));
      return false;
    }

    return true;
  }


  int64_t WFile::Length() {
    ::LARGE_INTEGER lInteger;
    if( ::GetFileSizeEx(
      file_handler_,
      &lInteger
      ) == FALSE ) {
        WIN32_API_WARNING(_xT("GetFileSizeEx"));
        return 0;
    }
    // ok here
    return static_cast<int64_t>( lInteger.QuadPart );
  }

  size_t WFile::Load( void* buffer , size_t size ) {
    // Read a file from existed handle

    DWORD dwRead ;
    if( ::ReadFile(file_handler_,buffer,DWORD(size),&dwRead,NULL) == FALSE ) {
      WIN32_API_WARNING(_xT("ReadFile"));
      return 0;
    } 
    return dwRead;
  }

  int64_t WFile::Seek( int64_t offset , int position ) {
    ::LARGE_INTEGER lInteger ,lNewPos;
    lInteger.QuadPart = offset;
    if( position == File::POS_BEG ) {
      ::SetFilePointerEx( file_handler_ , lInteger , &lNewPos , FILE_BEGIN );
    } else if( position == File::POS_CUR ) {
      ::SetFilePointerEx( file_handler_ , lInteger , &lNewPos , FILE_CURRENT );
    } else if( position == File::POS_END ) {
      ::SetFilePointerEx( file_handler_ , lInteger , &lNewPos , FILE_END );
    } else {
      UNREACH_BRANCH(_xT("Can not reaching here!"));
    }
    return static_cast<int64_t>( lInteger.QuadPart );
  }

  WFile::~WFile() {
    if( file_handler_ != INVALID_HANDLE_VALUE ) 
      if( ::CloseHandle(file_handler_) == FALSE ) 
        WIN32_API_WARNING(_xT("CloseHandle"));
  }

  // Directory
  bool WDirectory::CreateDirectoryInternal( const char_t* current_path ) {
    if( WFileSystem::IsArchiveExisted(current_path) ) {
      // existed 
      current_path_ = current_path;
      current_path_.reserve( current_path_.size()+ MAX_PATH+1);
      // ok
      return true;
    }
    SYS_LOG(PANIC,true,_xT("Can not create directory :%s!\nIt has already existed."),current_path);
    return false;
  }


}// namespace

#include "xfsm.h"
#include "xge.h"

// internal register routine implementation
namespace xge{
namespace xge_internal{
  bool RegsiterLocalFileSystem() {
    // TODO:: add register implementation
    return XGameEngine::GetFileSystemManager()->RegisterFileSystem(new WFileSystem());
  }
}
}